// exception.cc
//	Entry point into the Nachos kernel from user programs.
//	There are two kinds of things that can cause control to
//	transfer back to here from user code:
//
//	syscall -- The user code explicitly requests to call a procedure
//	in the Nachos kernel.  Right now, the only function we support is
//	"Halt".
//
//	exceptions -- The user code does something that the CPU can't handle.
//	For instance, accessing memory that doesn't exist, arithmetic errors,
//	etc.  
//
//	Interrupts (which can also cause control to transfer from user
//	code into the Nachos kernel) are handled elsewhere.
//
// For now, this only handles the Halt() system call.
// Everything else core dumps.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "syscall.h"
#include <stdio.h>
#include <iostream>
#ifdef NETWORK
#include "network.h"
#include "../network/post.h"
#endif
using namespace std;

int copyin(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes from the current thread's virtual address vaddr.
    // Return the number of bytes so read, or -1 if an error occors.
    // Errors can generally mean a bad virtual address was passed in.
    bool result;
    int n = 0; // The number of bytes copied in
    int *paddr = new int;

    while (n >= 0 && n < len) {
        result = machine->ReadMem(vaddr, 1, paddr);
        while (!result) // FALL 09 CHANGES
        {
            result = machine->ReadMem(vaddr, 1, paddr); // FALL 09 CHANGES: TO HANDLE PAGE FAULT IN THE ReadMem SYS CALL
        }

        buf[n++] = *paddr;

        if (!result) {
            //translation failed
            return -1;
        }

        vaddr++;
    }

    delete paddr;
    return len;
}

int copyout(unsigned int vaddr, int len, char *buf) {
    // Copy len bytes to the current thread's virtual address vaddr.
    // Return the number of bytes so written, or -1 if an error
    // occors.  Errors can generally mean a bad virtual address was
    // passed in.
    bool result;
    int n = 0; // The number of bytes copied in

    while (n >= 0 && n < len) {
        // Note that we check every byte's address
        result = machine->WriteMem(vaddr, 1, (int) (buf[n++]));

        if (!result) {
            //translation failed
            return -1;
        }

        vaddr++;
    }

    return n;
}

SpaceId Exec_SysCall(unsigned int vaddr) {
    // Create the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  No
    // way to return errors, though...

    int len;
    SpaceId rv;
    char *buf = new char[MAXFILENAME + 1]; // Kernel buffer to put the name in

    if (!buf) return -1;

    len = copyin(vaddr, MAXFILENAME + 1, buf);
    if (len == -1) {
        printf("ExecSysCall: Bad pointer passed to Exec.\n");
        delete buf;
        return -1;
    }

    buf[len] = '\0';

    rv = pm ->Exec(buf);
    delete[] buf;
    return rv;
}

void Create_Syscall(unsigned int vaddr, int len) {
    // Create the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  No
    // way to return errors, though...
    char *buf = new char[len + 1]; // Kernel buffer to put the name in

    if (!buf) return;

    if (copyin(vaddr, len, buf) == -1) {
        printf("%s", "Bad pointer passed to Create\n");
        delete buf;
        return;
    }

    buf[len] = '\0';

    fileSystem->Create(buf, 0);
    delete[] buf;
    return;
}

int Open_Syscall(unsigned int vaddr, int len) {
    // Open the file with the name in the user buffer pointed to by
    // vaddr.  The file name is at most MAXFILENAME chars long.  If
    // the file is opened successfully, it is put in the address
    // space's file table and an id returned that can find the file
    // later.  If there are any errors, -1 is returned.
    char *buf = new char[len + 1]; // Kernel buffer to put the name in
    OpenFile *f; // The new open file
    int id; // The openfile id

    if (!buf) {
        printf("%s", "Can't allocate kernel buffer in Open\n");
        return -1;
    }

    if (copyin(vaddr, len, buf) == -1) {
        printf("%s", "Bad pointer passed to Open\n");
        delete[] buf;
        return -1;
    }

    buf[len] = '\0';

    f = fileSystem->Open(buf);
    delete[] buf;

    if (f) {
        if ((id = currentThread->space->fileTable.Put(f)) == -1)
            delete f;
        return id;
    } else
        return -1;
}

void Write_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one. For disk files, the file is looked
    // up in the current address space's open file table and used as
    // the target of the write.

    char *buf; // Kernel buffer for output
    OpenFile *f; // Open file for output

    if (id == ConsoleInput) return;

    if (!(buf = new char[len])) {
        printf("%s", "Error allocating kernel buffer for write!\n");
        return;
    } else {
        if (copyin(vaddr, len, buf) == -1) {
            printf("%s", "Bad pointer passed to to write: data not written\n");
            delete[] buf;
            return;
        }
    }

    if (id == ConsoleOutput) {
        for (int ii = 0; ii < len; ii++) {
            printf("%c", buf[ii]);
        }

    } else {
        if ((f = (OpenFile *) currentThread->space->fileTable.Get(id))) {
            f->Write(buf, len);
        } else {
            printf("%s", "Bad OpenFileId passed to Write\n");
            len = -1;
        }
    }

    delete[] buf;
}

int Read_Syscall(unsigned int vaddr, int len, int id) {
    // Write the buffer to the given disk file.  If ConsoleOutput is
    // the fileID, data goes to the synchronized console instead.  If
    // a Write arrives for the synchronized Console, and no such
    // console exists, create one.    We reuse len as the number of bytes
    // read, which is an unnessecary savings of space.
    char *buf; // Kernel buffer for input
    OpenFile *f; // Open file for output

    if (id == ConsoleOutput) return -1;

    if (!(buf = new char[len])) {
        printf("%s", "Error allocating kernel buffer in Read\n");
        return -1;
    }

    if (id == ConsoleInput) {
        //Reading from the keyboard
        scanf("%s", buf);

        if (copyout(vaddr, len, buf) == -1) {
            printf("%s", "Bad pointer passed to Read: data not copied\n");
        }
    } else {
        if ((f = (OpenFile *) currentThread->space->fileTable.Get(id))) {
            len = f->Read(buf, len);
            if (len > 0) {
                //Read something from the file. Put into user's address space
                if (copyout(vaddr, len, buf) == -1) {
                    printf("%s", "Bad pointer passed to Read: data not copied\n");
                }
            }
        } else {
            printf("%s", "Bad OpenFileId passed to Read\n");
            len = -1;
        }
    }

    delete[] buf;
    return len;
}

void Close_Syscall(int fd) {
    // Close the file associated with id fd.  No error reporting.
    OpenFile *f = (OpenFile *) currentThread->space->fileTable.Remove(fd);

    if (f) {
        delete f;
    } else {
        printf("%s", "Tried to close an unopen file\n");
    }
}

//***********************************************add by dachao li*****************
#ifdef NETWORK
int Send_Syscall(int faraddr, unsigned int msg){
  int len = 0;
	char buf[MaxMailSize];
	bool result;
	int* temp = new int;
	//copy the msg into buf[],since I don't know the length, I don't use copyin.
		//but it is much the same as copyin
	while (len < MaxMailSize-1) {
        // just repeate ReadMem for the first char, because the first ReadMem always fails ---dachao li
		if(len==0)
			machine->ReadMem(msg, 1, temp);
        result = machine->ReadMem(msg, 1, temp);

        if (!result) {
            //translation failed
			DEBUG('#',"SEND_SYSCALL: readmem failed\n");
            return 0;
        }
		buf[len] = *temp;
		if(buf[len++] == '\0'){
			break;
		}
        msg++;
    }
	if(len == MaxMailSize){
  	DEBUG('#',"SEND_SYSCALL: warning, message reaches maximum length: %d,should no longer than %d\n", len, MaxMailSize);
  	//return 0;
  }
	DEBUG('#',"SEND_SYSCALL: message to send:%s, len:%d\n", buf, len);
	//make the send packet
  PacketHeader outPktHdr;
  MailHeader outMailHdr;
  outPktHdr.to = faraddr;		
  outMailHdr.to = 0;
  outMailHdr.from = 1;
  outMailHdr.length = len + 1;

  bool success = postOffice->Send(outPktHdr, outMailHdr, buf);
  if(!success){
  	DEBUG('#',"SEND_SYSCALL: send syscall to %d failed\n", faraddr);
  	return 0;
  }
  DEBUG('#',"SEND_SYSCALL: send syscall to %d succeeded\n", faraddr);
  return 1;
}

int Recv_Syscall(unsigned int msg){
	PacketHeader inPktHdr;
  MailHeader inMailHdr;
	int len = 0;
	char buf[MaxMailSize];
	bool result;
  DEBUG('#',"RECV_SYSCALL: listening incoming msg...\n");
  postOffice->Receive(0, &inPktHdr, &inMailHdr, buf);
	DEBUG('#',"RECV_SYSCALL: got msg from %d:%s, len:%d\n",inPktHdr.from, buf, inMailHdr.length);
	//copy buf into msg 
	while (len < MaxMailSize) {
        // just repeate WriteMem for the first char, because the first WriteMem always fails ---dachao li
        if(len==0)
			result = machine->WriteMem(msg, 1, (int) (buf[len]));
		result = machine->WriteMem(msg, 1, (int) (buf[len]));

        if (!result) {
            //translation failed
			DEBUG('#',"RECV_SYSCALL: WriteMem failed\n");
            return -1;
        }
		if(buf[len] == '\0'){
			DEBUG('#',"RECV_SYSCALL: end of msg break\n");
			break;
		}
		len++;
        msg++;
    }
	//return the sender's machineID
	return (int) inPktHdr.from;
}
#endif

void ExceptionHandler(ExceptionType which) {
    int type = machine->ReadRegister(2); // Which syscall?
    int rv = 0; // the return value from a syscall

    if (which == SyscallException) {
        switch (type) {
            default:
                DEBUG('a', "Unknown syscall - shutting down.\n");
            case SC_Halt:
                DEBUG('a', "Shutdown, initiated by user program.\n");
                interrupt->Halt();
                break;
            case SC_Create:
                DEBUG('a', "Create syscall.\n");
                Create_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_Open:
                DEBUG('a', "Open syscall.\n");
                rv = Open_Syscall(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_Write:
                DEBUG('a', "Write syscall.\n");
                Write_Syscall(machine->ReadRegister(4),
                        machine->ReadRegister(5),
                        machine->ReadRegister(6));
                break;
            case SC_Read:
                DEBUG('a', "Read syscall.\n");
                rv = Read_Syscall(machine->ReadRegister(4),
                        machine->ReadRegister(5),
                        machine->ReadRegister(6));
                break;
            case SC_Close:
                DEBUG('a', "Close syscall.\n");
                Close_Syscall(machine->ReadRegister(4));
                break;
            case SC_Exec:
                DEBUG('1', "ExceptionHandler: In EXEC system call\n");
                rv = Exec_SysCall(machine->ReadRegister(4));
                break;
            case SC_Exit:
                DEBUG('1', "ExceptionHandler: In EXIT System call\n");
                pm -> Exit(machine->ReadRegister(4));
                break;
            case SC_Fork:
                DEBUG('1', "ExceptionHandler: In FORK System call\n");
                pm -> createNewThread(machine->ReadRegister(4));
                break;
            case SC_Yield:
                DEBUG('1', "ExceptionHandler: In YIELD System call\n");
                currentThread -> Yield();
                break;
            case SC_Create_Condition:
                DEBUG('3', "ExceptionHandler: In CREATE_CONDITION system call\n");
                rv = pm -> createCondition();
                break;
            case SC_Destroy_Condition:
                DEBUG('3', "ExceptionHandler: In DESTROY_CONDITION system call\n");
                rv = pm -> destroyCondition(machine->ReadRegister(4));
                break;
            case SC_Wait_Condition:
                DEBUG('3', "ExceptionHandler: In WAIT_CONDITION system call\n");
                rv = pm -> waitOnCondition(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_Signal_Condition:
                DEBUG('3', "ExceptionHandler: In SIGNAL_CONDITION system call\n");
                rv = pm -> signalOnCondition(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_Broadcast_Condition:
                DEBUG('3', "ExceptionHandler: In BORADCAST_CONDITION system call\n");
                rv = pm -> broadCastOnCondition(machine->ReadRegister(4), machine->ReadRegister(5));
                break;
            case SC_CreateLock:
                DEBUG('3', "ExceptionHandler: In CREATE_LOCK system call\n");
                rv = pm -> createLock();
                break;
            case SC_DestoryLock:
                DEBUG('3', "ExceptionHandler: In DESTROY_LOCK system call\n");
                rv = pm -> destroyLock(machine->ReadRegister(4));
                break;
            case SC_LockAcquire:
                DEBUG('3', "ExceptionHandler: In ACQUIRE_LOCK system call\n");
                rv = pm -> acquireLock(machine->ReadRegister(4));
                break;
            case SC_LockRelease:
                DEBUG('3', "ExceptionHandler: In RELEASE_LOCK system call\n");
                rv = pm -> releaseLock(machine->ReadRegister(4));
                break;
#ifdef NETWORK
						case SC_Send:
                DEBUG('#', "ExceptionHandler: In SEND system call,to %d\n", machine->ReadRegister(4));
                rv = Send_Syscall(machine->ReadRegister(4),machine->ReadRegister(5));
                break;
            case SC_Recv:
                DEBUG('#', "ExceptionHandler: In RECV system call\n");
                rv = Recv_Syscall(machine->ReadRegister(4));
                break;
			case SC_GetMachineID:
				DEBUG('#', "ExceptionHandler: In GetMachineID system call\n");
				rv = postOffice-> getNetAddr();
				break;
#endif
        }

        // Put in the return value and increment the PC
        machine->WriteRegister(2, rv);
        machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
        machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
        machine->WriteRegister(NextPCReg, machine->ReadRegister(PCReg) + 4);
        return;
    }
    else if (which == PageFaultException) {
        //increment the stats
        stats->numPageFaults++;
        //load the required address.
        currentThread->space->LoadAddress(machine->ReadRegister(BadVAddrReg));
    }
    else {
        // print the IPT, TLB and PageTable. Used for debugging purpose.
        currentThread -> space -> onOtherException();
        cout << "Unexpected user mode exception - which:" << which << "  type:" << type << endl;
        interrupt->Halt();
    }
}
